'    Copyright 2008 Jan Molnar <jan.molnar@abds.de>
'
'    This file is part of JCQ.

'    JCQ is free software: you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation, either version 2 of the License, or
'    (at your option) any later version.

'    JCQ is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.

'    You should have received a copy of the GNU General Public License
'    along with JCQ. If not, see <http://www.gnu.org/licenses/>.

Imports System.Xml

Public Class XmlSerializer
    Implements ISerializer

    Private ReadOnly _ValueFormatters As New Dictionary(Of Type, IValueFormatter)
    Private ReadOnly _ReferenceFormatters As New Dictionary(Of Type, IReferenceFormatter)

    Private _ObjectsToSerialize As Queue(Of Object)
    Private _ObjectsSerialized As List(Of Object)

    Private _SerializeObjectIdDictionary As Dictionary(Of Object, Integer)
    Private _SerializeObjectId As Integer

    Private _DeserializeObjectIdDictionary As Dictionary(Of Integer, Object)
    Private _PropertyValueFixUps As List(Of PropertyValueFixUp)
    Private _ListItemFixUps As List(Of ListItemFixUp)
    Private _ReadOnlyListPropertyFixUps As List(Of ReadOnlyListPropertyFixUp)
    Private _CustomFixUps As List(Of IFixUp)

    Public Function GetSerializeObjectId(ByVal graph As Object) As Integer Implements ISerializer.GetSerializeObjectId
        Dim objectId = 0

        If Not _SerializeObjectIdDictionary.TryGetValue(graph, objectId) Then
            _ObjectsToSerialize.Enqueue(graph)

            objectId = Threading.Interlocked.Increment(_SerializeObjectId)

            _SerializeObjectIdDictionary.Add(graph, objectId)
        End If

        Return objectId
    End Function

    Public Sub InitializeForUnitTest()
        _SerializeObjectId = 0
        _SerializeObjectIdDictionary = New Dictionary(Of Object, Integer)
        _ObjectsToSerialize = New Queue(Of Object)
    End Sub

    'Public Function Serialize(ByVal graph As Object) As System.Xml.XmlDocument Implements ISerializer.Serialize
    '    Dim type As Type = graph.GetType

    '    _document = New XmlDocument
    '    _pendingObjects = New Queue(Of Object)
    '    _doneObjects = New List(Of Object)
    '    _ids = New Dictionary(Of Object, Integer)
    '    _idIterator = 1

    '    Dim root As XmlElement = _document.CreateElement("root")
    '    Dim rootId As Integer = GetObjectId(graph)

    '    Dim rootIdAttribute As XmlAttribute = _document.CreateAttribute("__rootId")
    '    rootIdAttribute.Value = CStr(rootId)
    '    root.Attributes.Append(rootIdAttribute)

    '    Do Until _pendingObjects.Count = 0
    '        Dim obj As Object = _pendingObjects.Dequeue
    '        _doneObjects.Add(obj)

    '        Dim f As IReferenceFormatter = Me.GetReferenceFormatter(obj.GetType)

    '        Dim element As XmlElement

    '        element = f.Serialize(obj)

    '        root.AppendChild(element)
    '    Loop

    '    _document.AppendChild(root)

    '    Return _document
    'End Function

    Public Function GetDeserializeObjectById(ByVal id As Integer) As Object Implements ISerializer.GetDeserializeObjectById
        Return _DeserializeObjectIdDictionary(id)
    End Function

    Public Sub RegisterValueFormatter(ByVal type As Type, ByVal f As IValueFormatter) Implements ISerializer.RegisterValueFormatter
        _ValueFormatters.Add(type, f)
    End Sub

    Public Sub RegisterReferenceFormatter(ByVal type As Type, ByVal f As IReferenceFormatter) Implements ISerializer.RegisterReferenceFormatter
        _ReferenceFormatters.Add(type, f)
    End Sub

    Public Function GetValueFormatter(ByVal type As Type) As IValueFormatter Implements ISerializer.GetValueFormatter
        Dim formatter As IValueFormatter = Nothing

        If Not _ValueFormatters.TryGetValue(type, formatter) Then
            formatter = New XmlValueFormatter(Me, type)
            _ValueFormatters.Add(type, formatter)
        End If

        Return formatter
    End Function

    Public Function GetReferenceFormatter(ByVal type As Type) As IReferenceFormatter Implements ISerializer.GetReferenceFormatter
        Dim formatter As IReferenceFormatter = Nothing

        If Not _ReferenceFormatters.TryGetValue(type, formatter) Then
            If type.GetInterface(GetType(IList).ToString) IsNot Nothing Then
                formatter = New DefaultIListReferenceFormatter(Me, type)
                _ReferenceFormatters.Add(type, formatter)
            Else
                formatter = New DefaultReferenceFormatter(Me, type)
                _ReferenceFormatters.Add(type, formatter)
            End If
        End If

        Return formatter
    End Function

    'Public Function Deserialize(ByVal doc As XmlDocument) As Object Implements ISerializer.Deserialize
    '    _objects = New Dictionary(Of Integer, Object)
    '    _valueReferences = New List(Of ValueReference)
    '    _itemReferences = New List(Of ItemReference)

    '    _ReadOnlyIListReferences = New List(Of ReadOnlyIListReference)

    '    _FixupCallbacks = New List(Of Action(Of ISerializer))

    '    Dim root As XmlElement = DirectCast(doc.ChildNodes(0), XmlElement)
    '    Dim rootId As Integer = CInt(root.Attributes("__rootId").Value)

    '    For Each element As XmlElement In root.ChildNodes
    '        Dim typeName As String = element.Attributes("__type").Value
    '        Dim type As Type = type.GetType(typeName)

    '        Dim f As IReferenceFormatter = Me.GetReferenceFormatter(type)

    '        f.Deserialize(element)
    '    Next

    '    For Each ref As ValueReference In _valueReferences
    '        Dim value As Object = _objects(ref.Value)

    '        ref.PropertyDescriptor.SetValue(ref.Graph, value)
    '    Next

    '    For Each ref As ItemReference In _itemReferences
    '        Dim value As Object = _objects(ref.Value)

    '        ref.Graph.Add(value)
    '    Next

    '    For Each ref As ReadOnlyIListReference In _ReadOnlyIListReferences
    '        Dim value As IList = DirectCast(_objects(ref.ObjectId), IList)

    '        For Each item In value
    '            ref.List.Add(item)
    '        Next
    '    Next

    '    For Each cl As Action(Of ISerializer) In _FixupCallbacks
    '        cl.DynamicInvoke(Me)
    '    Next

    '    Dim objroot As Object = _objects(rootId)

    '    _objects = Nothing
    '    _valueReferences = Nothing
    '    _itemReferences = Nothing
    '    _FixupCallbacks = Nothing

    '    Return objroot
    'End Function

    Public Sub RegisterObject(ByVal id As Integer, ByVal graph As Object) Implements ISerializer.RegisterObject
        _DeserializeObjectIdDictionary.Add(id, graph)
    End Sub

    Public Sub RegisterPropertyValueFixUp(ByVal fixup As PropertyValueFixUp) Implements ISerializer.RegisterPropertyValueFixUp
        _PropertyValueFixUps.Add(fixup)
    End Sub

    Public Sub RegisterListItemFixUp(ByVal fixup As ListItemFixUp) Implements ISerializer.RegisterListItemFixUp
        _ListItemFixUps.Add(fixup)
    End Sub

    Public Sub RegisterReadOnlyListPropertyFixUp(ByVal fixup As ReadOnlyListPropertyFixUp) Implements ISerializer.RegisterReadOnlyListPropertyFixUp
        _ReadOnlyListPropertyFixUps.Add(fixup)
    End Sub

    Public Sub RegisterCustomFixUp(ByVal fixup As IFixUp) Implements ISerializer.RegisterCustomFixUp
        _CustomFixUps.Add(fixup)
    End Sub

    Public Sub Serialize(ByVal graph As Object, ByVal writer As System.Xml.XmlWriter) Implements ISerializer.Serialize
        Dim rootId As Integer

        _ObjectsToSerialize = New Queue(Of Object)
        _ObjectsSerialized = New List(Of Object)
        _SerializeObjectIdDictionary = New Dictionary(Of Object, Integer)
        _SerializeObjectId = 0

        rootId = GetSerializeObjectId(graph)

        writer.WriteStartElement("root")
        writer.WriteAttributeString("__rootId", rootId.ToString)

        Dim obj As Object
        Dim formatter As IReferenceFormatter

        Do Until _ObjectsToSerialize.Count = 0
            obj = _ObjectsToSerialize.Dequeue
            _ObjectsSerialized.Add(obj)

            formatter = Me.GetReferenceFormatter(obj.GetType)
            formatter.Serialize(obj, writer)
        Loop

        writer.WriteEndElement()

        _ObjectsToSerialize.Clear()
        _ObjectsToSerialize = Nothing

        _ObjectsSerialized.Clear()
        _ObjectsSerialized = Nothing

        _SerializeObjectIdDictionary.Clear()
        _SerializeObjectIdDictionary = Nothing

        _SerializeObjectId = Nothing
    End Sub

    Public Function Deserialize(ByVal reader As System.Xml.XmlReader) As Object Implements ISerializer.Deserialize
        Dim rootId As Integer

        _DeserializeObjectIdDictionary = New Dictionary(Of Integer, Object)
        _PropertyValueFixUps = New List(Of PropertyValueFixUp)
        _ListItemFixUps = New List(Of ListItemFixUp)
        _ReadOnlyListPropertyFixUps = New List(Of ReadOnlyListPropertyFixUp)
        _CustomFixUps = New List(Of IFixUp)

        '_FixupCallbacks = New List(Of Action(Of ISerializer))

        ' Check for data
        If Not reader.Read Then Return Nothing

        reader.MoveToFirstAttribute() ' This is always __rootId
        rootId = Integer.Parse(reader.Value)

        Do While reader.Read
            If Not reader.NodeType = XmlNodeType.Element Then Continue Do

            Dim typeName As String = reader.GetAttribute("__type")
            Dim type As Type = type.GetType(typeName)
            Dim formatter As IReferenceFormatter = Me.GetReferenceFormatter(type)

            formatter.Deserialize(reader)
        Loop

        For Each fixup In _PropertyValueFixUps
            fixup.Execute(Me)
        Next

        For Each fixup In _ListItemFixUps
            fixup.Execute(Me)
        Next

        For Each fixup As ReadOnlyListPropertyFixUp In _ReadOnlyListPropertyFixUps
            fixup.Execute(Me)
        Next

        For Each fixup In _CustomFixUps
            fixup.Execute(Me)
        Next

        Dim objroot As Object = GetDeserializeObjectById(rootId)

        _DeserializeObjectIdDictionary.Clear()
        _DeserializeObjectIdDictionary = Nothing

        _PropertyValueFixUps.Clear()
        _PropertyValueFixUps = Nothing

        _ListItemFixUps.Clear()
        _ListItemFixUps = Nothing

        _ReadOnlyListPropertyFixUps.Clear()
        _ReadOnlyListPropertyFixUps = Nothing

        _CustomFixUps.Clear()
        _CustomFixUps = Nothing

        Return objroot
    End Function

End Class